package HJwt

import (
	"fmt"
	"github.com/golang-jwt/jwt/v4"
	gonanoid "github.com/matoous/go-nanoid/v2"
	"time"
)

type config struct {
	// 签名:   HS256、HS384、HS512   RS256、RS384、RS512  ES256、ES384、ES512
	sig string
	// 加密秘钥
	key []byte
	// 加密私钥路径
	priKeyPath string
	// 加密私钥
	priKey []byte
	// 加密公钥路径
	pubKeyPath string
	// 加密公钥密钥
	pubKey []byte
	// 过期时间, 单位为 秒, 默认过期时间为: 1h
	expiresAt time.Duration
	// 单位: 秒， 刷新令牌的时间 > expiresAt， 默认: 7天
	maxRefresh time.Duration
	// 单位: 秒, 定义在某个时间之前，该jwt都是不可用的
	notBefore time.Duration
	// jwt的签发时间 单位: 秒
	issuedAt time.Duration
	// Payload
	mapClaims jwt.MapClaims
	// jti唯一标识
	jti string
	// 当前时间
	nowTime time.Time
}

var (
	// HS256、HS384、HS512   RS256、RS384、RS512  ES256、ES384、ES512
	signMethods = map[string]jwt.SigningMethod{
		"HS256": jwt.SigningMethodHS256,
		"HS384": jwt.SigningMethodHS384,
		"HS512": jwt.SigningMethodHS512,
		"RS256": jwt.SigningMethodRS256,
		"RS384": jwt.SigningMethodRS384,
		"RS512": jwt.SigningMethodRS512,
		"ES256": jwt.SigningMethodES256,
		"ES384": jwt.SigningMethodES384,
		"ES512": jwt.SigningMethodES512,
		"NONE":  jwt.SigningMethodNone,
	}
)

type GJwt interface {
	// CreateToken 生成token
	CreateToken(claims jwt.MapClaims) (token Token, err error)
	// Verify 验证token
	Verify(token string) (claims jwt.MapClaims, err error)
}

func New(opts ...Options) GJwt {
	c := &config{
		expiresAt: 3600,
		sig:       "NONE",
		key:       []byte("JWTNONE"),
	}

	if len(opts) > 0 {
		for _, opt := range opts {
			opt(c)
		}
	}

	return c
}

func (c *config) Verify(tokenStr string) (claims jwt.MapClaims, err error) {
	pToken, err := jwt.Parse(tokenStr, c.getVeryKey())
	m := make(jwt.MapClaims, 5)
	if pToken == nil || !pToken.Valid {
		return m, err
	}
	if _, ok := pToken.Claims.(jwt.MapClaims); ok {
		m = pToken.Claims.(jwt.MapClaims)
	}
	return m, nil
}

func (c *config) getVeryKey() jwt.Keyfunc {
	return func(t *jwt.Token) (interface{}, error) {
		if _, ok := signMethods[c.sig]; !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
		}
		if c.sig == "HS256" || c.sig == "HS384" || c.sig == "HS512" {
			return c.key, nil
		}

		if c.sig == "ES256" || c.sig == "ES384" || c.sig == "ES512" {
			return jwt.ParseECPublicKeyFromPEM(c.pubKey)
		}

		if c.sig == "RS256" || c.sig == "RS384" || c.sig == "RS512" {
			return jwt.ParseRSAPublicKeyFromPEM(c.pubKey)
		}
		return c.key, nil
	}
}

// CreateToken 生成令牌
func (c *config) CreateToken(claims jwt.MapClaims) (token Token, err error) {
	c.jti = getJti()
	c.nowTime = time.Now().Add(c.getIat())
	token.AccessToken, err = c.sign(jwt.NewWithClaims(c.getSignMethod(), c.getClaims(claims)))
	if err != nil {
		return
	}
	token.Jti = c.jti
	token.AccessTime = c.nowTime.Add(c.getExp()).Unix()
	// 生成刷新令牌
	token.RefreshToken, err = c.sign(jwt.NewWithClaims(c.getSignMethod(), c.getRefreshClaims()))
	if err != nil {
		return
	}
	token.RefreshTime = c.nowTime.Add(c.getRefreshTime()).Unix()
	return
}

func (c *config) sign(token *jwt.Token) (string, error) {
	if c.sig == "HS256" || c.sig == "HS384" || c.sig == "HS512" {
		return token.SignedString(c.key)
	}

	if c.sig == "ES256" || c.sig == "ES384" || c.sig == "ES512" {
		key, err := jwt.ParseECPrivateKeyFromPEM(c.priKey)
		if err != nil {
			return "", err
		}
		return token.SignedString(key)
	}

	if c.sig == "RS256" || c.sig == "RS384" || c.sig == "RS512" {
		key, err := jwt.ParseRSAPrivateKeyFromPEM(c.priKey)
		if err != nil {
			return "", err
		}
		return token.SignedString(key)
	}
	return token.SignedString(c.key)
}

func (c *config) getRefreshClaims() jwt.MapClaims {
	m := make(jwt.MapClaims, 2)
	m["jti"] = c.jti
	m["exp"] = jwt.NewNumericDate(c.nowTime.Add(c.getRefreshTime()))
	return m
}

func (c *config) getClaims(claims jwt.MapClaims) jwt.MapClaims {
	c.initClaims()
	if len(claims) > 0 {
		for k, v := range claims {
			if _, ok := c.mapClaims[k]; !ok {
				c.mapClaims[k] = v
			}
		}
	}
	return c.mapClaims
}

func (c *config) initClaims() {
	c.mapClaims = make(jwt.MapClaims, 4)
	// 默认过期时间为: 1小时
	c.mapClaims["exp"] = jwt.NewNumericDate(c.nowTime.Add(c.getExp()))
	// jwt不可用时间
	c.mapClaims["nbf"] = jwt.NewNumericDate(c.nowTime.Add(c.getNbf()))
	c.mapClaims["iat"] = jwt.NewNumericDate(c.nowTime)
	c.mapClaims["jti"] = c.jti
}

func getJti() string {
	id, _ := gonanoid.New()
	return id
}

// 获取刷新令牌的过期时间
func (c *config) getRefreshTime() time.Duration {
	if c.maxRefresh == 0 {
		c.maxRefresh = 7 * 24 * 3600
	}
	return c.maxRefresh*time.Second + c.getExp()
}

func (c *config) getSignMethod() jwt.SigningMethod {
	if _, ok := signMethods[c.sig]; ok {
		return signMethods[c.sig]
	}
	return jwt.SigningMethodNone
}

func (c *config) getIat() time.Duration {
	if c.issuedAt <= 0 {
		c.issuedAt = 0
	}
	return c.issuedAt * time.Second
}

func (c *config) getNbf() time.Duration {
	if c.notBefore == 0 {
		c.notBefore = 1
	}

	if c.notBefore < 0 {
		c.notBefore = 0 - c.notBefore
	}

	duration, _ := time.ParseDuration(fmt.Sprintf("-%ds", c.notBefore))
	return duration
}

func (c *config) getExp() time.Duration {
	if c.expiresAt == 0 {
		c.expiresAt = 3600
	}
	return c.expiresAt * time.Second
}
